package com.teamfractal.fracdustry.common.blockentity.Generators;

import com.mojang.datafixers.DSL;
import com.teamfractal.fracdustry.common.block.impl.generators.FDMicrowaveGeneratorBlock;
import com.teamfractal.fracdustry.common.container.Generators.FDMicrowaveGeneratorContainer;
import com.teamfractal.fracdustry.common.container.datasync.SimpleArray;
import com.teamfractal.fracdustry.common.tools.ItemCharge;
import com.teamfractal.fracdustry.common.util.energystorage.FDEnergyStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.registries.ObjectHolder;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicInteger;

import static com.teamfractal.fracdustry.common.tools.worldCheck.isNoBlockAboveAt;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class FDMicrowaveGeneratorBlockEntity extends BlockEntity implements MenuProvider {
    public static final String NAME = "fracdustry:microwave_generator";

    @ObjectHolder(NAME)
    public static BlockEntityType<FDMicrowaveGeneratorBlockEntity> BLOCK_ENTITY_TYPE;

    private final ItemStackHandler itemHandler = createHandler();
    private final FDEnergyStorage energyStorage = createEnergy();

    private final LazyOptional<IItemHandler> handler = LazyOptional.of(() -> itemHandler);
    private final LazyOptional<FDEnergyStorage> energy = LazyOptional.of(() -> energyStorage);

    private final SimpleArray currentEnergy = new SimpleArray();
    private final SimpleArray maxEnergy = new SimpleArray();

    @SubscribeEvent
    public static void onRegisterBlockEntityType(@Nonnull RegistryEvent.Register<BlockEntityType<?>> event) {
        event.getRegistry().register(BlockEntityType.Builder.
                of(FDMicrowaveGeneratorBlockEntity::new, FDMicrowaveGeneratorBlock.BLOCK).build(DSL.remainderType()).setRegistryName(NAME));
    }

    @Nonnull
    @Override
    public Component getDisplayName() {
        return new TextComponent("");
    }

    public AbstractContainerMenu createMenu(int windowId, @Nonnull Inventory playerInventory, @Nonnull Player playerEntity) {

        if (level != null) {
            return new FDMicrowaveGeneratorContainer(windowId, level, worldPosition, playerInventory, playerEntity, currentEnergy, maxEnergy);
        }
        return null;
    }

    //Disable capabilities when block is removed
    @Override
    public void setRemoved() {
        super.setRemoved();
        handler.invalidate();
        energy.invalidate();
    }
    //Block's energy generation and sound playing

    public void tickServer(BlockState state) {
        boolean canGenerateEnergy = level != null && isNoBlockAboveAt(level, worldPosition.above());
        //todo:make energy generation adjustable in configs
        if(canGenerateEnergy) {
            energyStorage.addEnergy(1);
            setChanged();
        }


        /*if ( level.getGameTime() % 20 == 0 && canGenerateEnergy) {
            level.playSound(null, worldPosition, FDSounds.thermal_generator_loop.get(), SoundSource.BLOCKS, 1, 1);
        }*/
        //Currently no extra conditions for activating the generator.
        BlockState blockState = null;
        if (level != null) {
            blockState = level.getBlockState(worldPosition);
        }
        if (blockState != null) {
            level.setBlock(worldPosition, blockState.setValue(BlockStateProperties.POWERED, isNoBlockAboveAt(level, worldPosition.above())),
                    3);
        }
        new ItemCharge(energyStorage,itemHandler.getStackInSlot(0),this,100);
        sendOutPower();
    }

    //Sending energy to neighbor blocks
    private void sendOutPower() {
        AtomicInteger capacity = new AtomicInteger(energyStorage.getEnergyStored());
        if (capacity.get() > 0) {
            Direction direction = this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING);
            BlockEntity te = null;
            if (level != null && direction == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING)) {
                te = level.getBlockEntity(worldPosition.relative(direction));
            }
            if (te != null) {
                boolean doContinue = te.getCapability(CapabilityEnergy.ENERGY, direction.getOpposite()).map(handler ->
                {if (handler.canReceive()) {
                    int received = handler.receiveEnergy(Math.min(capacity.get(), 100), false);
                    capacity.addAndGet(-received);
                    energyStorage.consumeEnergy(received);
                    setChanged();
                    return capacity.get() > 0;
                } else {
                    return true;
                }
                }).orElse(true);
                if (!doContinue) {
                    return;
                }
            }
        }
        currentEnergy.set(0,energyStorage.getEnergyStored());
        maxEnergy.set(0,energyStorage.getMaxEnergyStored());
    }

    public FDMicrowaveGeneratorBlockEntity(BlockPos pos, BlockState state)
    {
        super(BLOCK_ENTITY_TYPE,pos,state);
    }

    //NBT saving and loading
    @Nonnull
    @Override
    public CompoundTag save(@Nonnull  CompoundTag tag) {
        tag.put("inv", itemHandler.serializeNBT());
        energy.ifPresent(h -> h.serializeNBT(tag));

        return super.save(tag);
    }

    @Override
    public void load(@Nonnull CompoundTag tag) {
        itemHandler.deserializeNBT(tag.getCompound("inv"));
        energy.ifPresent(h -> h.deserializeNBT(tag));
        super.load(tag);
    }


    private FDEnergyStorage createEnergy() {
        return new FDEnergyStorage(10000, 100) {
            @Override
            protected void onEnergyChanged() {
                setChanged();
            }

            @Override
            public boolean canExtract(){
                return true;
            }
        };
    }

    //ItemStack Handler
    private ItemStackHandler createHandler() {
        return new ItemStackHandler(1) {

            @Override
            protected void onContentsChanged(int slot) {
                setChanged();
            }

            @Override
            public boolean isItemValid(int slot, @Nonnull ItemStack stack) {
                return true;
            }

            @Nonnull
            @Override
            public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) {
                return super.insertItem(slot, stack, simulate);
            }
        };
    }

    @Nonnull
    @Override
    public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
        if (cap == CapabilityEnergy.ENERGY) {
            boolean isValid = (side != null && side.getAxis().isHorizontal()
                    && side == this.getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING));
            return isValid?energy.cast():super.getCapability(cap, side);
        }
        if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
            return handler.cast();
        }
        return super.getCapability(cap, side);
    }
}
